function Node()
{
    this.ID = "";
    this.Weight = 0;
    this.Volume = 0;
    this.Longitude = 0;
    this.Latitude = 0;

    this.initialize = function(id, weight, volume, longitude, latitude)
    {
        this.ID = id;
        this.Weight = weight;
        this.Volume = volume;
        this.Longitude = longitude;
        this.Latitude = latitude;
    };

    this.toString = function()
    {
        return $.toJSON(this);
    };
}

Node.getDistance = function(node1, node2)
{
    var x1 = node1.Longitude;
    var y1 = node1.Latitude;
    var x2 = node2.Longitude;
    var y2 = node2.Latitude;

    //TODO change this using getDirection from google.maps
    var x = Math.abs(x2 - x1);
    var y = Math.abs(y2 - y1);
    var d = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
    return d;
}

///DeliveryOrder
function DeliveryOrder(id, weight, volume, longitude, latitude)
{
    var _me = this;

    this.Distance = 0;
    this.Delivered = false;

    this.initialize(id, weight, volume, longitude, latitude);
}
DeliveryOrder.prototype = new Node();
DeliveryOrder.prototype.constructor = DeliveryOrder;
//DeliveryOrder

//
function CarGroup(id, maxDistance, maxWeight, maxVolume)
{
    var _me = this;

    this.MaxDistance = maxDistance;
    this.MaxWeight = maxWeight;
    this.MaxVolume = maxVolume;
    this.Path = [];

    this.initialize(id, 0, 0, 0, 0);

    this.getCarID = function()
    {
        var carID = _me.ID;
        if(_me.Path.length > 0)
            carID += "(" + _me.Path[_me.Path.length - 1].ID + ")";
        return carID;
    };

    this.deliver = function(deliveryOrder)
    {
        var d = Node.getDistance(_me, deliveryOrder);
        var w = parseInt(_me.Weight) + parseInt(deliveryOrder.Weight);
        var v = parseInt(_me.Volume) + parseInt(deliveryOrder.Volume);

        if(d > _me.MaxDistance || w > _me.MaxWeight || v > _me.MaxVolume)
            return false;

        deliveryOrder.Distance = d;
        _me.Longitude = deliveryOrder.Longitude;
        _me.Latitude = deliveryOrder.Latitude;
        _me.Weight = w;
        _me.Volume = v;
        _me.Path[_me.Path.length] = deliveryOrder;
        return true;
    };

    this.isDelivered = function(deliveryOrder)
    {
        for(var i = 0; i < _me.Path.length; i++)
        {
            if(_me.Path[i].ID == deliveryOrder.ID)
                return true;
        }

        return false;
    };

    this.cloneObject = function()
    {
        var carGroup = new CarGroup(_me.ID, _me.MaxDistance, _me.MaxWeight, _me.MaxVolume);
        carGroup.Weight = _me.Weight;
        carGroup.Volume = _me.Volume;
        carGroup.Path = _me.Path;
        carGroup.Longitude = _me.Longitude;
        carGroup.Latitude = _me.Latitude;
        return carGroup;
    }
}
CarGroup.prototype = new Node();
CarGroup.prototype.constructor = CarGroup;

function Warehouse()
{
    var _me = this;

    this.CarGroupList = [];
    this.addCarGroup = function(carGroup)
    {
        _me.CarGroupList[_me.CarGroupList.length] = carGroup;
    };

    this.getCarGroup = function(index)
    {
        return _me.CarGroupList[index];
    };

    this.DeliveryOrderList = [];
    this.addDeliveryOrder = function(deliveryOrder)
    {
        _me.DeliveryOrderList[_me.DeliveryOrderList.length] = deliveryOrder;
    };

    this.getDeliveryOrder = function(index)
    {
        return _me.DeliveryOrderList[index];
    };

    /*
     * find nearest DeliveryOrder from specified CarGroup position
     */
    this.getNearestDO = function(carGroup)
    {
        var deliveryOrder = null;
        var lastDistance = 0;
        for(var i=0; i<_me.DeliveryOrderList.length; i++)
        {
            var curDO = _me.DeliveryOrderList[i];
            if(curDO.Delivered)
                continue;

            var distance = Node.getDistance(carGroup, curDO);

            if(deliveryOrder == null || distance < lastDistance)
            {
                deliveryOrder = curDO;
                lastDistance = distance;
            }
        }

        return deliveryOrder;
    };

    /*
     * get all available DeliveryOrder within carGroup.MaxDistance range
     */
    this.getAvailableDO = function(carGroup)
    {
        var doList = [];
        if(carGroup == null) return doList;

        for(var i=0; i<_me.DeliveryOrderList.length; i++)
        {
            var deliveryOrder = _me.DeliveryOrderList[i];
            if(carGroup.isDelivered(deliveryOrder) || deliveryOrder.Delivered) continue;

            var distance = Node.getDistance(carGroup, deliveryOrder);
            deliveryOrder.Distance = distance;
            if(distance <= carGroup.MaxDistance)
                doList[doList.length] = deliveryOrder;
        }
        return doList;
    }
}

function Loader()
{
    var _me = this;

    this.Warehouse = new Warehouse();

    var _carGroupTemplate = [];
    this.loadCarGroupTemplate = function()
    {
        //TODO get from DB
        _carGroupTemplate[_carGroupTemplate.length] = new CarGroup("BXXXAX", 25, 60, 30);
        /*_carGroupTemplate[_carGroupTemplate.length] = new CarGroup("BXXXAY", 34, 60, 20);
        _carGroupTemplate[_carGroupTemplate.length] = new CarGroup("BXXXAZ", 25, 60, 20);*/
    };

    /*
     * generate all CarGroup for all pending DeliveryOrders
     */
    this.generate = function()
    {
        _me.loadCarGroupTemplate();
        for(var i=0; i<_carGroupTemplate.length; i++)
        {
            var carGroup = _carGroupTemplate[i];
            var nearestDO = _me.Warehouse.getNearestDO(carGroup);
            var newCarGroup = _me.calculate(carGroup, nearestDO);
            if(newCarGroup == null) continue;
            for(var j=0; j<newCarGroup.Path.length; j++)
            {
                var path = newCarGroup.Path[j];
                path.Delivered = true;
            }
            if(newCarGroup.Path.length > 0)
                _me.Warehouse.addCarGroup(newCarGroup);
        }
        //TODO check whether there are still ungrouped DO, if there is then call generate again until there are no more ungrouped DO
    };

    /*
     * Calculate all DO
     */
    this.calculate = function(carGroup, deliveryOrder)
    {
        if(deliveryOrder == null) return carGroup;
        Loader.out += "<br />Car("+carGroup.getCarID()+") : " + carGroup.Weight + ", " + carGroup.Volume + " to " + deliveryOrder.Weight + ", " + deliveryOrder.Volume + "<br />";
        var ret = carGroup.deliver(deliveryOrder);
        if(!ret) return null;
        Loader.out += deliveryOrder.ID + " : " + deliveryOrder.Weight + ", " + deliveryOrder.Volume + " = " + carGroup.Weight + ", " + carGroup.Volume + " ---> " + (ret ? "TRUE" : "FALSE") + "<br />";

        var curCarGroup = null;
        var availableDO = _me.Warehouse.getAvailableDO(carGroup);
        for(var i=0; i<availableDO.length; i++)
        {
            var curDO = availableDO[i];
            var newCarGroup = _me.calculate(carGroup.cloneObject(), curDO);
            if(newCarGroup == null) continue;
            if(curCarGroup == null || newCarGroup.Volume > curCarGroup.Volume)
            {
                curCarGroup = newCarGroup;
            }
        }

        if(curCarGroup == null)
            curCarGroup = carGroup;

        return curCarGroup;
    }
}

Loader.out = "";